# -*- coding: utf-8 -*-
"""
分类服务层
目的：封装分类相关的业务逻辑，提供统一的分类操作接口
职责：处理分类的增删改查等核心业务逻辑
"""
from datetime import datetime
from typing import Optional
from sqlalchemy.orm import Session
from app.models.category import Category
from app.models.article import Article
from app.config.database import SessionLocal
from app.utils.response import Result


class CategoryService:
    """
    分类业务逻辑服务类
    目的：提供分类相关的所有业务操作，将业务逻辑与控制器层解耦
    职责：分类的增删改查、数据验证、业务规则处理
    使用场景：所有分类相关的业务操作都通过此服务类处理
    """

    @staticmethod
    def create_category(name: str, description: Optional[str] = None,
                       parent_id: Optional[int] = None,
                       sort_order: int = 0) -> Result:
        """
        创建新分类
        目的：处理分类创建的业务逻辑，包括数据验证和存储
        功能：验证必填字段、验证父分类是否存在、检查分类名称重复、创建分类记录

        Args:
            name (str): 分类名称，必填，最长100字符
            description (Optional[str]): 分类描述，可选
            parent_id (Optional[int]): 父分类ID，可选，None表示顶级分类
            sort_order (int): 排序顺序，默认0，数字越小越靠前

        Returns:
            Result: 统一响应结果对象
                - 成功时返回创建的分类信息
                - 失败时返回错误消息
        """
        # 创建数据库会话对象，用于执行数据库操作
        db: Session = SessionLocal()

        try:
            # 验证必填字段
            if not name or not name.strip():
                return Result.validation_error('分类名称为必填项')

            # 验证分类名称长度
            if len(name.strip()) > 100:
                return Result.validation_error('分类名称不能超过100个字符')

            # 检查同一层级下分类名称是否重复
            existing_category = db.query(Category).filter(
                Category.name == name.strip(),
                Category.parent_id == parent_id
            ).first()

            if existing_category:
                return Result.validation_error('同一层级下已存在相同名称的分类')

            # 如果指定了父分类，验证父分类是否存在且状态为启用
            if parent_id is not None:
                parent_category = db.query(Category).filter(
                    Category.id == parent_id,
                    Category.status == 1
                ).first()

                if not parent_category:
                    return Result.validation_error('父分类不存在或已被禁用')

            # 创建分类对象，初始化所有必要字段
            category = Category(
                name=name.strip(),
                description=description.strip() if description else None,
                parent_id=parent_id,
                sort_order=sort_order,
                status=1  # 新创建的分类默认为启用状态
            )

            # 将分类对象添加到数据库会话
            db.add(category)
            # 提交事务，保存到数据库
            db.commit()
            # 刷新对象，获取数据库生成的ID等字段
            db.refresh(category)

            return Result.success(data=category.to_dict(), message='分类创建成功')

        except Exception as e:
            # 发生异常时回滚事务，保证数据一致性
            db.rollback()
            # 返回错误信息
            return Result.internal_error(f'分类创建失败：{str(e)}')

        finally:
            # 关闭数据库会话，释放连接资源
            db.close()

    @staticmethod
    def update_category(category_id: int, name: Optional[str] = None,
                       description: Optional[str] = None,
                       parent_id: Optional[int] = None,
                       sort_order: Optional[int] = None,
                       status: Optional[int] = None) -> Result:
        """
        更新分类信息
        目的：修改指定分类的信息，支持部分字段更新
        功能：验证分类存在性、验证更新数据、更新分类记录

        Args:
            category_id (int): 分类ID，必填
            name (Optional[str]): 分类名称，可选更新
            description (Optional[str]): 分类描述，可选更新
            parent_id (Optional[int]): 父分类ID，可选更新
            sort_order (Optional[int]): 排序顺序，可选更新
            status (Optional[int]): 分类状态，可选更新，0-禁用，1-启用

        Returns:
            Result: 统一响应结果对象
                - 成功时返回更新后的分类信息
                - 失败时返回错误消息
        """
        # 创建数据库会话对象
        db: Session = SessionLocal()

        try:
            # 查询要更新的分类
            category = db.query(Category).filter(Category.id == category_id).first()

            # 如果分类不存在，返回404错误
            if not category:
                return Result.not_found('分类不存在')

            # 如果要更新分类名称，验证名称有效性和重复性
            if name is not None:
                name = name.strip()
                if not name:
                    return Result.validation_error('分类名称不能为空')

                if len(name) > 100:
                    return Result.validation_error('分类名称不能超过100个字符')

                # 检查同一层级下是否存在相同名称的其他分类
                # 使用 parent_id 参数如果提供了，否则使用现有的 parent_id
                check_parent_id = parent_id if parent_id is not None else category.parent_id
                existing_category = db.query(Category).filter(
                    Category.name == name,
                    Category.parent_id == check_parent_id,
                    Category.id != category_id  # 排除自己
                ).first()

                if existing_category:
                    return Result.validation_error('同一层级下已存在相同名称的分类')

                category.name = name

            # 如果要更新父分类，验证父分类存在性，并防止循环引用
            if parent_id is not None:
                # 不能将分类的父分类设置为自己
                if parent_id == category_id:
                    return Result.validation_error('不能将分类的父分类设置为自己')

                # 验证父分类是否存在且状态为启用
                parent_category = db.query(Category).filter(
                    Category.id == parent_id,
                    Category.status == 1
                ).first()

                if not parent_category:
                    return Result.validation_error('父分类不存在或已被禁用')

                # 防止循环引用：检查要设置的父分类是否是当前分类的子分类
                if CategoryService._is_descendant(db, category_id, parent_id):
                    return Result.validation_error('不能将分类的父分类设置为其子分类')

                category.parent_id = parent_id

            # 更新描述
            if description is not None:
                category.description = description.strip() if description.strip() else None

            # 更新排序顺序
            if sort_order is not None:
                category.sort_order = sort_order

            # 更新状态
            if status is not None:
                if status not in [0, 1]:
                    return Result.validation_error('分类状态参数错误，必须为0(禁用)或1(启用)')

                # 如果要禁用分类，检查是否有文章使用该分类
                if status == 0:
                    article_count = db.query(Article).filter(
                        Article.category_id == category_id
                    ).count()

                    if article_count > 0:
                        return Result.validation_error(f'该分类下有{article_count}篇文章，无法禁用')

                category.status = status

            # 提交事务
            db.commit()
            # 刷新对象
            db.refresh(category)

            return Result.success(data=category.to_dict(), message='分类更新成功')

        except Exception as e:
            # 发生异常时回滚事务
            db.rollback()
            return Result.internal_error(f'分类更新失败：{str(e)}')

        finally:
            # 关闭数据库会话
            db.close()

    @staticmethod
    def delete_category(category_id: int, force: bool = False) -> Result:
        """
        删除分类
        目的：删除指定的分类，支持软删除和强制删除
        功能：验证分类存在性、检查是否有文章使用、检查是否有子分类、删除分类

        Args:
            category_id (int): 分类ID，必填
            force (bool): 是否强制删除，默认False
                - False: 软删除，将状态设置为禁用
                - True: 物理删除，直接从数据库删除记录

        Returns:
            Result: 统一响应结果对象
                - 成功时返回成功消息
                - 失败时返回错误消息
        """
        # 创建数据库会话对象
        db: Session = SessionLocal()

        try:
            # 查询要删除的分类
            category = db.query(Category).filter(Category.id == category_id).first()

            # 如果分类不存在，返回404错误
            if not category:
                return Result.not_found('分类不存在')

            # 检查是否有文章使用该分类
            article_count = db.query(Article).filter(
                Article.category_id == category_id
            ).count()

            if article_count > 0:
                return Result.validation_error(f'该分类下有{article_count}篇文章，无法删除')

            # 检查是否有子分类
            child_count = db.query(Category).filter(
                Category.parent_id == category_id
            ).count()

            if child_count > 0:
                return Result.validation_error(f'该分类下有{child_count}个子分类，无法删除')

            # 根据删除方式处理
            if force:
                # 强制删除：物理删除
                db.delete(category)
                message = '分类删除成功'
            else:
                # 软删除：设置状态为禁用
                category.status = 0
                message = '分类已禁用'

            # 提交事务
            db.commit()

            return Result.success(message=message)

        except Exception as e:
            # 发生异常时回滚事务
            db.rollback()
            return Result.internal_error(f'分类删除失败：{str(e)}')

        finally:
            # 关闭数据库会话
            db.close()

    @staticmethod
    def get_category_by_id(category_id: int) -> Result:
        """
        根据ID获取分类详情
        目的：查询指定ID的分类信息
        功能：从数据库查询分类详情

        Args:
            category_id (int): 分类ID

        Returns:
            Result: 统一响应结果对象
                - 成功时返回分类详细信息
                - 失败时返回404错误
        """
        # 创建数据库会话对象
        db: Session = SessionLocal()

        try:
            # 根据ID查询分类
            category = db.query(Category).filter(Category.id == category_id).first()

            # 如果分类不存在，返回404错误
            if not category:
                return Result.not_found('分类不存在')

            return Result.success(data=category.to_dict(), message='获取分类成功')

        except Exception as e:
            return Result.internal_error(f'获取分类失败：{str(e)}')

        finally:
            # 关闭数据库会话
            db.close()

    @staticmethod
    def get_categories_tree(status: Optional[int] = 1) -> Result:
        """
        获取分类树形结构
        目的：查询所有分类并组织成树形结构，用于前端显示
        功能：根据状态筛选分类，按父子关系组织成树形结构

        Args:
            status (Optional[int]): 分类状态筛选，1-启用，0-禁用，None-全部

        Returns:
            Result: 统一响应结果对象
                - 成功时返回树形结构的分类列表
                - 失败时返回错误消息
        """
        # 创建数据库会话对象
        db: Session = SessionLocal()

        try:
            # 构建查询对象
            query = db.query(Category)

            # 如果指定了状态，添加状态筛选条件
            if status is not None:
                query = query.filter(Category.status == status)

            # 按排序字段和ID排序
            categories = query.order_by(Category.sort_order, Category.id).all()

            # 构建分类字典，便于快速查找
            category_dict = {cat.id: cat.to_dict() for cat in categories}

            # 为每个分类添加children数组
            for cat_id, cat_data in category_dict.items():
                cat_data['children'] = []

            # 构建树形结构
            tree = []
            for cat_id, cat_data in category_dict.items():
                parent_id = cat_data['parent_id']
                if parent_id is None:
                    # 顶级分类，添加到根数组
                    tree.append(cat_data)
                elif parent_id in category_dict:
                    # 子分类，添加到父分类的children数组
                    category_dict[parent_id]['children'].append(cat_data)

            return Result.success(data=tree, message='获取分类树成功')

        except Exception as e:
            return Result.internal_error(f'获取分类树失败：{str(e)}')

        finally:
            # 关闭数据库会话
            db.close()

    @staticmethod
    def _is_descendant(db: Session, ancestor_id: int, descendant_id: int) -> bool:
        """
        检查一个分类是否是另一个分类的子孙分类（递归检查）
        目的：防止分类循环引用，确保分类树的完整性
        功能：递归查询父分类关系，判断是否存在循环

        Args:
            db (Session): 数据库会话对象
            ancestor_id (int): 祖先分类ID
            descendant_id (int): 后代分类ID

        Returns:
            bool: True表示descendant_id是ancestor_id的子孙分类，False表示不是
        """
        # 查询 descendant_id 的父分类
        category = db.query(Category).filter(Category.id == descendant_id).first()

        if not category or category.parent_id is None:
            # 没有父分类，不是子孙关系
            return False

        if category.parent_id == ancestor_id:
            # 父分类就是祖先分类，是子孙关系
            return True

        # 递归检查父分类
        return CategoryService._is_descendant(db, ancestor_id, category.parent_id)
